Skip to content

refactor: ESM migration#523

Merged
jens-maus merged 1 commit into
jens-maus:masterfrom
KristjanESPERANTO:esm
Jul 18, 2026
Merged

refactor: ESM migration#523
jens-maus merged 1 commit into
jens-maus:masterfrom
KristjanESPERANTO:esm

Conversation

@KristjanESPERANTO

@KristjanESPERANTO KristjanESPERANTO commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

This change follows up on issue #521 and moves node-ical to an ESM-first setup, while keeping CommonJS support in place for now.

It turned out to take a bit more than expected. To make the module split cleanly, I moved a fair amount of logic out of the files ical.js and node-ical.js into smaller, focused modules. That makes the code easier to maintain and gives a cleaner base for the module format transition.

I checked compatibility against our main consumers (ioBroker.ical and MagicMirror), and both still work with this setup.

Summary by CodeRabbit

  • New Features
    • Added dual ESM/CommonJS entry-point support (including a default export) with updated typings.
    • Recurring event expansion improvements for overrides and exclusions.
  • Documentation
    • Documented supported module formats and updated examples/build instructions for current entry points.
  • Bug Fixes
    • Updated the Windows time zones updater to use timed, stricter HTTP fetching.
  • Tests
    • Migrated tests to ESM and added coverage for ESM/CJS parity.
  • Chores
    • Added an automated CommonJS build step and adjusted CI/test execution to match.

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6b4c9a54-465e-43cf-a633-06058f083380

📥 Commits

Reviewing files that changed from the base of the PR and between 917d505 and ef02ed9.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (44)
  • .github/workflows/nodejs.yml
  • .gitignore
  • README.md
  • build/README.md
  • build/build-cjs.js
  • build/update-windows-zones.js
  • examples/example-rrule-basic.js
  • examples/example-rrule-datefns.js
  • examples/example-rrule-dayjs.js
  • examples/example-rrule-luxon.js
  • examples/example-rrule-moment.js
  • examples/example-rrule-vanilla.js
  • examples/example.js
  • ical.js
  • lib/core-api.js
  • lib/date-utils.js
  • lib/expand-recurring-event.js
  • lib/ical-parser-utils.js
  • lib/public-api.js
  • lib/temporal.js
  • lib/tz-utils.js
  • node-ical.d.ts
  • node-ical.js
  • package.json
  • test/advanced.test.js
  • test/async-error-handling.test.js
  • test/basic.test.js
  • test/commonjs-compat.test.js
  • test/date-only-rrule-until.test.js
  • test/examples-snapshot.test.js
  • test/expand-recurring-event.test.js
  • test/extended-components.test.js
  • test/from-url.test.js
  • test/google-calendar-until-bug.test.js
  • test/issue-459-exchange-recurrence-id.test.js
  • test/meta-preservation.test.js
  • test/module-formats.test.js
  • test/monthly-bymonthday-multiple.test.js
  • test/non-utc-until.test.js
  • test/polyfill-check.test.js
  • test/regression-fixes.test.js
  • test/tsconfig.json
  • test/tz-utils.test.js
  • tsconfig.json
🚧 Files skipped from review as they are similar to previous changes (37)
  • test/monthly-bymonthday-multiple.test.js
  • test/commonjs-compat.test.js
  • test/from-url.test.js
  • lib/temporal.js
  • test/date-only-rrule-until.test.js
  • build/build-cjs.js
  • examples/example.js
  • examples/example-rrule-luxon.js
  • test/tsconfig.json
  • test/basic.test.js
  • .gitignore
  • test/non-utc-until.test.js
  • .github/workflows/nodejs.yml
  • build/README.md
  • examples/example-rrule-moment.js
  • lib/date-utils.js
  • lib/public-api.js
  • examples/example-rrule-vanilla.js
  • test/tz-utils.test.js
  • test/expand-recurring-event.test.js
  • tsconfig.json
  • node-ical.d.ts
  • README.md
  • test/google-calendar-until-bug.test.js
  • package.json
  • node-ical.js
  • test/async-error-handling.test.js
  • examples/example-rrule-basic.js
  • lib/tz-utils.js
  • test/extended-components.test.js
  • test/regression-fixes.test.js
  • test/issue-459-exchange-recurrence-id.test.js
  • build/update-windows-zones.js
  • lib/core-api.js
  • ical.js
  • lib/expand-recurring-event.js
  • test/module-formats.test.js

📝 Walkthrough

Walkthrough

The package migrates to ESM with explicit CommonJS compatibility, shared parser and recurring-event modules, generated build artifacts, updated timezone tooling, converted examples and tests, and revised package documentation.

Changes

ESM package and calendar processing

Layer / File(s) Summary
Package entrypoints and module runtime
package.json, node-ical.js, node-ical.d.ts, ical.js, lib/*.js
Package metadata exposes ESM and CommonJS entrypoints, while runtime modules use shared ESM exports and API wiring.
Shared parser and recurrence utilities
ical.js, lib/ical-parser-utils.js, lib/expand-recurring-event.js
Parsing, date handling, timezone resolution, recurrence normalization, component storage, EXDATE handling, and recurring expansion are centralized in shared utilities.
Build and timezone data generation
build/*, package.json, .github/workflows/nodejs.yml, .gitignore
A CommonJS bundle and Windows timezone data are generated through updated scripts and CI steps.
Examples, tests, and compiler migration
examples/*, test/*, tsconfig.json, test/tsconfig.json
Examples and tests use ESM imports, module-format parity and regression behavior are validated, and TypeScript settings use Node ESM resolution.
Module usage documentation
README.md, build/README.md
Documentation describes ESM and CommonJS imports, runtime entry files, typings, example execution, and the updated Windows-zones script.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant node_ical_js
  participant createCoreApi
  participant ical_js
  participant expandRecurringEvent
  Caller->>node_ical_js: import or require package
  node_ical_js->>createCoreApi: construct sync and async APIs
  Caller->>ical_js: parseICS input
  ical_js-->>Caller: parsed calendar
  Caller->>expandRecurringEvent: expand event range
  expandRecurringEvent-->>Caller: sorted instances
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 32.88% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: migrating the package to ESM while preserving CommonJS support.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (2)
test/module-formats.test.js (1)

49-76: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicated key-parity check across two tests.

The key-parity loop at Lines 61–63 (for (const key of Object.keys(esm.default)) { assert.ok(Object.hasOwn(cjs, key), ...) }) is repeated verbatim at Lines 73–75 in the next test. Consider consolidating into a shared helper or merging the assertions into a single test to avoid drift.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/module-formats.test.js` around lines 49 - 76, Consolidate the duplicated
ESM/CJS key-parity loop currently present in both tests. Either merge the
assertions into one test or extract a shared helper and reuse it from the tests,
while preserving the existing missing-key assertion message and API checks.
node-ical.d.ts (1)

112-125: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider more precise types for internal properties currently typed unknown.

objectHandlers, handleObject, and parseLines are typed as unknown, which gives consumers no type safety if they access these properties. If this is intentional to discourage direct use, consider adding a brief comment explaining that. Otherwise, typing them (e.g., as Record<string, (...args: unknown[]) => unknown> and (...args: unknown[]) => unknown) would improve the developer experience for advanced consumers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@node-ical.d.ts` around lines 112 - 125, Improve the declarations for
objectHandlers, handleObject, and parseLines in the default export object:
either replace unknown with accurate callable/record types for these internal
properties, or add a brief comment documenting that unknown is intentional to
discourage direct consumer use. Preserve the existing exported API shape and
type the properties consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@build/update-windows-zones.js`:
- Around line 18-45: Update fetchText to enforce a request timeout so stalled
CLDR requests reject instead of hanging CI. Prefer replacing the manual
https.get redirect flow with Node 20’s fetch and AbortSignal.timeout(), while
preserving non-success HTTP errors and returning the response text for
successful requests.

In `@ical.js`:
- Around line 463-475: Update the named exports for handleObject, parseLines,
and parseICS in ical.js to bind each function to the ical object so calls retain
the required this context; apply the same binding treatment to the equivalent
exports in node-ical.js, while leaving objectHandlers unchanged.

In `@lib/expand-recurring-event.js`:
- Around line 94-120: Update getEventDurationMs and calculateEndTime to
represent full-day durations as calendar-day counts rather than fixed
milliseconds. For full-day events, compute the span between local calendar dates
and advance the start using local date arithmetic so DST transitions preserve
the correct end date; keep timed-event millisecond duration behavior unchanged.
- Around line 345-358: Update the recurring-instance expansion around
buildRecurringInstance so it also inspects event.recurrences overrides whose
effective start falls within the requested range, even when their RECURRENCE-ID
is outside searchFrom..searchTo. Merge these override-derived instances with the
RRULE results, deduplicate instances already produced through RRULE expansion,
then retain the existing range filtering and start-time sorting.

In `@lib/ical-parser-utils.js`:
- Around line 41-46: Update copyRecordFields to iterate and copy only source own
properties using Object.hasOwn, and avoid assignment-based writes for the
__proto__ UID by defining UID-keyed properties with Object.defineProperty or
using null-prototype records throughout the related merge/recurrence path. Apply
the same protection to the code around the UID handling at the referenced later
section, preserving normal field-copy behavior for other keys.
- Around line 721-733: Update freebusyParameter so it invokes the FREEBUSY
storage handler returned or registered by storeParameter, rather than merely
calling storeParameter and discarding its result. Ensure the raw value and
parameters are persisted on fb before dateParameter processes the start and end
parts.
- Around line 185-194: Update applyImplicitEndDate for DATE entries to advance
entry.start by one calendar day while preserving local midnight, rather than
using utcAdd(..., 1, 'days'). Keep the existing date-time cloning behavior and
return flow unchanged.

In `@package.json`:
- Around line 4-24: Update the package.json engines.node constraint to require
Node.js >=20.10.0, ensuring the ESM entry using JSON import attributes is not
exposed to unsupported Node 20.0–20.9 versions.

---

Nitpick comments:
In `@node-ical.d.ts`:
- Around line 112-125: Improve the declarations for objectHandlers,
handleObject, and parseLines in the default export object: either replace
unknown with accurate callable/record types for these internal properties, or
add a brief comment documenting that unknown is intentional to discourage direct
consumer use. Preserve the existing exported API shape and type the properties
consistently.

In `@test/module-formats.test.js`:
- Around line 49-76: Consolidate the duplicated ESM/CJS key-parity loop
currently present in both tests. Either merge the assertions into one test or
extract a shared helper and reuse it from the tests, while preserving the
existing missing-key assertion message and API checks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6b141778-ebaf-4a2d-b1e9-418028e5ec77

📥 Commits

Reviewing files that changed from the base of the PR and between 55a6735 and 85ae175.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (42)
  • .github/workflows/nodejs.yml
  • .gitignore
  • README.md
  • build/README.md
  • build/build-cjs.js
  • build/update-windows-zones.js
  • examples/example-rrule-basic.js
  • examples/example-rrule-datefns.js
  • examples/example-rrule-dayjs.js
  • examples/example-rrule-luxon.js
  • examples/example-rrule-moment.js
  • examples/example-rrule-vanilla.js
  • examples/example.js
  • ical.js
  • lib/core-api.js
  • lib/date-utils.js
  • lib/expand-recurring-event.js
  • lib/ical-parser-utils.js
  • lib/public-api.js
  • lib/temporal.js
  • lib/tz-utils.js
  • node-ical.d.ts
  • node-ical.js
  • package.json
  • test/advanced.test.js
  • test/async-error-handling.test.js
  • test/basic.test.js
  • test/date-only-rrule-until.test.js
  • test/examples-snapshot.test.js
  • test/expand-recurring-event.test.js
  • test/extended-components.test.js
  • test/from-url.test.js
  • test/google-calendar-until-bug.test.js
  • test/issue-459-exchange-recurrence-id.test.js
  • test/meta-preservation.test.js
  • test/module-formats.test.js
  • test/monthly-bymonthday-multiple.test.js
  • test/non-utc-until.test.js
  • test/polyfill-check.test.js
  • test/tsconfig.json
  • test/tz-utils.test.js
  • tsconfig.json

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 8

🧹 Nitpick comments (2)
test/module-formats.test.js (1)

49-76: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicated key-parity check across two tests.

The key-parity loop at Lines 61–63 (for (const key of Object.keys(esm.default)) { assert.ok(Object.hasOwn(cjs, key), ...) }) is repeated verbatim at Lines 73–75 in the next test. Consider consolidating into a shared helper or merging the assertions into a single test to avoid drift.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/module-formats.test.js` around lines 49 - 76, Consolidate the duplicated
ESM/CJS key-parity loop currently present in both tests. Either merge the
assertions into one test or extract a shared helper and reuse it from the tests,
while preserving the existing missing-key assertion message and API checks.
node-ical.d.ts (1)

112-125: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider more precise types for internal properties currently typed unknown.

objectHandlers, handleObject, and parseLines are typed as unknown, which gives consumers no type safety if they access these properties. If this is intentional to discourage direct use, consider adding a brief comment explaining that. Otherwise, typing them (e.g., as Record<string, (...args: unknown[]) => unknown> and (...args: unknown[]) => unknown) would improve the developer experience for advanced consumers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@node-ical.d.ts` around lines 112 - 125, Improve the declarations for
objectHandlers, handleObject, and parseLines in the default export object:
either replace unknown with accurate callable/record types for these internal
properties, or add a brief comment documenting that unknown is intentional to
discourage direct consumer use. Preserve the existing exported API shape and
type the properties consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@build/update-windows-zones.js`:
- Around line 18-45: Update fetchText to enforce a request timeout so stalled
CLDR requests reject instead of hanging CI. Prefer replacing the manual
https.get redirect flow with Node 20’s fetch and AbortSignal.timeout(), while
preserving non-success HTTP errors and returning the response text for
successful requests.

In `@ical.js`:
- Around line 463-475: Update the named exports for handleObject, parseLines,
and parseICS in ical.js to bind each function to the ical object so calls retain
the required this context; apply the same binding treatment to the equivalent
exports in node-ical.js, while leaving objectHandlers unchanged.

In `@lib/expand-recurring-event.js`:
- Around line 94-120: Update getEventDurationMs and calculateEndTime to
represent full-day durations as calendar-day counts rather than fixed
milliseconds. For full-day events, compute the span between local calendar dates
and advance the start using local date arithmetic so DST transitions preserve
the correct end date; keep timed-event millisecond duration behavior unchanged.
- Around line 345-358: Update the recurring-instance expansion around
buildRecurringInstance so it also inspects event.recurrences overrides whose
effective start falls within the requested range, even when their RECURRENCE-ID
is outside searchFrom..searchTo. Merge these override-derived instances with the
RRULE results, deduplicate instances already produced through RRULE expansion,
then retain the existing range filtering and start-time sorting.

In `@lib/ical-parser-utils.js`:
- Around line 41-46: Update copyRecordFields to iterate and copy only source own
properties using Object.hasOwn, and avoid assignment-based writes for the
__proto__ UID by defining UID-keyed properties with Object.defineProperty or
using null-prototype records throughout the related merge/recurrence path. Apply
the same protection to the code around the UID handling at the referenced later
section, preserving normal field-copy behavior for other keys.
- Around line 721-733: Update freebusyParameter so it invokes the FREEBUSY
storage handler returned or registered by storeParameter, rather than merely
calling storeParameter and discarding its result. Ensure the raw value and
parameters are persisted on fb before dateParameter processes the start and end
parts.
- Around line 185-194: Update applyImplicitEndDate for DATE entries to advance
entry.start by one calendar day while preserving local midnight, rather than
using utcAdd(..., 1, 'days'). Keep the existing date-time cloning behavior and
return flow unchanged.

In `@package.json`:
- Around line 4-24: Update the package.json engines.node constraint to require
Node.js >=20.10.0, ensuring the ESM entry using JSON import attributes is not
exposed to unsupported Node 20.0–20.9 versions.

---

Nitpick comments:
In `@node-ical.d.ts`:
- Around line 112-125: Improve the declarations for objectHandlers,
handleObject, and parseLines in the default export object: either replace
unknown with accurate callable/record types for these internal properties, or
add a brief comment documenting that unknown is intentional to discourage direct
consumer use. Preserve the existing exported API shape and type the properties
consistently.

In `@test/module-formats.test.js`:
- Around line 49-76: Consolidate the duplicated ESM/CJS key-parity loop
currently present in both tests. Either merge the assertions into one test or
extract a shared helper and reuse it from the tests, while preserving the
existing missing-key assertion message and API checks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6b141778-ebaf-4a2d-b1e9-418028e5ec77

📥 Commits

Reviewing files that changed from the base of the PR and between 55a6735 and 85ae175.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (42)
  • .github/workflows/nodejs.yml
  • .gitignore
  • README.md
  • build/README.md
  • build/build-cjs.js
  • build/update-windows-zones.js
  • examples/example-rrule-basic.js
  • examples/example-rrule-datefns.js
  • examples/example-rrule-dayjs.js
  • examples/example-rrule-luxon.js
  • examples/example-rrule-moment.js
  • examples/example-rrule-vanilla.js
  • examples/example.js
  • ical.js
  • lib/core-api.js
  • lib/date-utils.js
  • lib/expand-recurring-event.js
  • lib/ical-parser-utils.js
  • lib/public-api.js
  • lib/temporal.js
  • lib/tz-utils.js
  • node-ical.d.ts
  • node-ical.js
  • package.json
  • test/advanced.test.js
  • test/async-error-handling.test.js
  • test/basic.test.js
  • test/date-only-rrule-until.test.js
  • test/examples-snapshot.test.js
  • test/expand-recurring-event.test.js
  • test/extended-components.test.js
  • test/from-url.test.js
  • test/google-calendar-until-bug.test.js
  • test/issue-459-exchange-recurrence-id.test.js
  • test/meta-preservation.test.js
  • test/module-formats.test.js
  • test/monthly-bymonthday-multiple.test.js
  • test/non-utc-until.test.js
  • test/polyfill-check.test.js
  • test/tsconfig.json
  • test/tz-utils.test.js
  • tsconfig.json
🛑 Comments failed to post (8)
build/update-windows-zones.js (1)

18-45: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify AbortSignal.timeout() and fetch() availability in Node 20+
node -e "
const hasFetch = typeof fetch === 'function';
const hasAbortTimeout = typeof AbortSignal.timeout === 'function';
console.log('fetch:', hasFetch);
console.log('AbortSignal.timeout:', hasAbortTimeout);
if (!hasFetch || !hasAbortTimeout) process.exit(1);
"

Repository: jens-maus/node-ical

Length of output: 195


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the file structure and relevant symbols
ast-grep outline build/update-windows-zones.js --view expanded || true

# Read the file with line numbers
wc -l build/update-windows-zones.js
cat -n build/update-windows-zones.js | sed -n '1,220p'

# Check whether https is used elsewhere in the file
rg -n "\bhttps\b|fetchText|AbortSignal\.timeout|globalThis\.fetch|fetch\(" build/update-windows-zones.js

Repository: jens-maus/node-ical

Length of output: 7600


Add a timeout to fetchText to avoid CI hangs.
https.get() here can wait indefinitely if the CLDR endpoint stalls. Since this script runs on Node 20+, switching to fetch() with AbortSignal.timeout() would also remove the manual redirect handling.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@build/update-windows-zones.js` around lines 18 - 45, Update fetchText to
enforce a request timeout so stalled CLDR requests reject instead of hanging CI.
Prefer replacing the manual https.get redirect flow with Node 20’s fetch and
AbortSignal.timeout(), while preserving non-success HTTP errors and returning
the response text for successful requests.
ical.js (1)

463-475: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Bind named exports that depend on this.

Directly importing handleObject, parseLines, or parseICS calls them with this === undefined; each accesses another method or property through this and will fail. Bind these exports to ical. The equivalent exports in node-ical.js need the same treatment.

Proposed fix
 const {
   objectHandlers,
-  handleObject,
-  parseLines,
-  parseICS,
 } = ical;
+const handleObject = ical.handleObject.bind(ical);
+const parseLines = ical.parseLines.bind(ical);
+const parseICS = ical.parseICS.bind(ical);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

const {
  objectHandlers,
} = ical;
const handleObject = ical.handleObject.bind(ical);
const parseLines = ical.parseLines.bind(ical);
const parseICS = ical.parseICS.bind(ical);

export {
  objectHandlers,
  handleObject,
  parseLines,
  parseICS,
};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ical.js` around lines 463 - 475, Update the named exports for handleObject,
parseLines, and parseICS in ical.js to bind each function to the ical object so
calls retain the required this context; apply the same binding treatment to the
equivalent exports in node-ical.js, while leaving objectHandlers unchanged.
lib/expand-recurring-event.js (2)

94-120: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Use calendar-day spans for full-day event ends.

Adding a fixed millisecond duration to local midnight produces 23:00 or 01:00 ends around DST transitions. Track full-day duration as a number of calendar dates and advance with local calendar arithmetic.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/expand-recurring-event.js` around lines 94 - 120, Update
getEventDurationMs and calculateEndTime to represent full-day durations as
calendar-day counts rather than fixed milliseconds. For full-day events, compute
the span between local calendar dates and advance the start using local date
arithmetic so DST transitions preserve the correct end date; keep timed-event
millisecond duration behavior unchanged.

345-358: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Include overrides moved into the requested range.

Only RRULE dates inside searchFrom..searchTo are inspected. An override keyed by an earlier RECURRENCE-ID but moved into this range is therefore omitted. Merge unique event.recurrences whose effective starts overlap the range, while deduplicating overrides already reached through RRULE expansion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/expand-recurring-event.js` around lines 345 - 358, Update the
recurring-instance expansion around buildRecurringInstance so it also inspects
event.recurrences overrides whose effective start falls within the requested
range, even when their RECURRENCE-ID is outside searchFrom..searchTo. Merge
these override-derived instances with the RRULE results, deduplicate instances
already produced through RRULE expansion, then retain the existing range
filtering and start-time sorting.
lib/ical-parser-utils.js (3)

41-46: 🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

Store UID keys without invoking object prototype setters.

A UID of __proto__ resolves to Object.prototype; the merge or recurrence path then writes parsed event fields onto the global prototype. Use Object.hasOwn, copy only own fields, and define UID properties with Object.defineProperty or use null-prototype records.

Proposed approach
+function setRecordField(target, key, value) {
+  Object.defineProperty(target, key, {
+    value,
+    enumerable: true,
+    configurable: true,
+    writable: true,
+  });
+}
+
 function copyRecordFields(target, source) {
-  for (const key in source) {
-    if (key !== null) {
-      target[key] = source[key];
-    }
+  for (const key of Object.keys(source)) {
+    setRecordField(target, key, source[key]);
   }
 }
...
-  if (parentEntry[uid] === undefined) {
-    parentEntry[uid] = sourceEntry;
+  if (!Object.hasOwn(parentEntry, uid)) {
+    setRecordField(parentEntry, uid, sourceEntry);

Also applies to: 99-120

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/ical-parser-utils.js` around lines 41 - 46, Update copyRecordFields to
iterate and copy only source own properties using Object.hasOwn, and avoid
assignment-based writes for the __proto__ UID by defining UID-keyed properties
with Object.defineProperty or using null-prototype records throughout the
related merge/recurrence path. Apply the same protection to the code around the
UID handling at the referenced later section, preserving normal field-copy
behavior for other keys.

185-194: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Advance DATE values by a calendar day, not 24 elapsed hours.

utcAdd(..., 1, 'days') adds exactly 24 hours. Across DST transitions, the implicit end can become 23:00 or 01:00 instead of local midnight on the next date.

Proposed fix
   if (entry.duration === undefined) {
-    entry.end = entry.datetype === 'date-time'
-      ? cloneDateWithMeta(entry.start)
-      : cloneDateWithMeta(entry.start, utcAdd(entry.start, 1, 'days'));
+    entry.end = cloneDateWithMeta(entry.start);
+    if (entry.datetype !== 'date-time') {
+      entry.end.setDate(entry.end.getDate() + 1);
+    }
     return entry;
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

function applyImplicitEndDate(entry, utcAdd) {
  if (entry.end) {
    return entry;
  }

  if (entry.duration === undefined) {
    entry.end = cloneDateWithMeta(entry.start);
    if (entry.datetype !== 'date-time') {
      entry.end.setDate(entry.end.getDate() + 1);
    }
    return entry;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/ical-parser-utils.js` around lines 185 - 194, Update applyImplicitEndDate
for DATE entries to advance entry.start by one calendar day while preserving
local midnight, rather than using utcAdd(..., 1, 'days'). Keep the existing
date-time cloning behavior and return flow unchanged.

721-733: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Invoke the FREEBUSY storage handler.

storeParameter(value, parameters, fb) only creates and discards a handler, so the raw FREEBUSY value and its parameters are never stored.

Proposed fix
-      storeParameter(value, parameters, fb);
+      storeParameter(name)(value, parameters, fb);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  const freebusyParameter = function (name) {
    return function (value, parameters, curr) {
      const fb = addFBType({}, parameters);
      curr[name] ||= [];
      curr[name].push(fb);

      storeParameter(name)(value, parameters, fb);

      const parts = value.split('/');

      for (const [index, partName] of ['start', 'end'].entries()) {
        dateParameter(partName)(parts[index], parameters, fb);
      }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/ical-parser-utils.js` around lines 721 - 733, Update freebusyParameter so
it invokes the FREEBUSY storage handler returned or registered by
storeParameter, rather than merely calling storeParameter and discarding its
result. Ensure the raw value and parameters are persisted on fb before
dateParameter processes the start and end parts.
package.json (1)

4-24: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the engines.node field and verify compatibility with import attributes
cat package.json | jq '.engines'

Repository: jens-maus/node-ical

Length of output: 178


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## package.json engines and module fields\n'
cat -n package.json | sed -n '1,80p'

printf '\n## referenced source snippets\n'
for f in lib/tz-utils.js lib/temporal.js; do
  echo "\n### $f"
  cat -n "$f" | sed -n '1,220p'
done

printf '\n## shipped entry files\n'
git ls-files 'node-ical.*' 'lib/*.js' 'windowsZones.json'

Repository: jens-maus/node-ical

Length of output: 11636


Bump engines.node to >=20.10.0. lib/tz-utils.js uses JSON import attributes, so >=20 still admits Node 20.0–20.9, which will fail to load the ESM entry.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@package.json` around lines 4 - 24, Update the package.json engines.node
constraint to require Node.js >=20.10.0, ensuring the ESM entry using JSON
import attributes is not exposed to unsupported Node 20.0–20.9 versions.

Source: Learnings

@KristjanESPERANTO

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@KristjanESPERANTO

Copy link
Copy Markdown
Contributor Author

@jens-maus coderabbit seems satisfied. What do you think?

@jens-maus

Copy link
Copy Markdown
Owner

@KristjanESPERANTO Thanks. this LGTM and is close to getting merged ;-) The only potential improvement would be, if you could add an additional test which ensures that CommonJS support (as long as it exists in node-ical) is still working. Thus, having a simple test that loads and processes something using CommonJS. Is that something that makes sense and feasible?

@KristjanESPERANTO

Copy link
Copy Markdown
Contributor Author

Yes, makes sense. I just added one :)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
test/examples-snapshot.test.js (1)

54-54: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Remove unnecessary v flag from regular expression.

The v (Unicode sets) flag is not required for this simple extension-matching regular expression. Additionally, since the v flag was introduced in Node.js 20, using it may cause a SyntaxError if this test is executed in older Node.js environments that might still be supported.

Consider removing the v flag for improved compatibility and simplicity.

♻️ Proposed fix
-      const snapshotFile = path.join(snapshotDir, script.replace(/\.(?:c|m)?js$/v, '.txt'));
+      const snapshotFile = path.join(snapshotDir, script.replace(/\.(?:c|m)?js$/, '.txt'));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/examples-snapshot.test.js` at line 54, Update the regular expression in
the snapshotFile path construction to remove the unnecessary v flag, while
preserving its existing extension-matching behavior and compatibility with older
Node.js versions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/ical-parser-utils.js`:
- Around line 754-758: Update the FREEBUSY period parsing around the `parts`
loop to distinguish start/end values from start/duration values. When the second
token is an RFC 5545 duration, parse it as a duration and derive `fb.end` from
the parsed start date; retain the existing `dateParameter` handling for explicit
end dates. Add coverage for both period representations.
- Around line 258-260: Update finalizeEndedComponent so applyImplicitEndDate is
skipped for VALARM components, identified via the component’s name/type, while
retaining the existing end-date derivation for other components. Ensure
repeating alarms with DURATION and no start do not pass undefined into utcAdd or
receive an end value.

In `@test/commonjs-compat.test.js`:
- Around line 27-29: Remove the blank line immediately after the opening brace
of the test callback in “loads the package through require() and parses a
VEVENT”, leaving the block unpadded while preserving the test logic.
- Around line 3-5: Remove the blank line between the ES import declarations and
the createRequire assignment in commonjs-compat.test.js, keeping the imports and
require setup contiguous to satisfy the import-x/order formatting rule.

---

Nitpick comments:
In `@test/examples-snapshot.test.js`:
- Line 54: Update the regular expression in the snapshotFile path construction
to remove the unnecessary v flag, while preserving its existing
extension-matching behavior and compatibility with older Node.js versions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d086ddac-696a-4735-8485-a777e729259b

📥 Commits

Reviewing files that changed from the base of the PR and between dc6dc1e and 666cdc5.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (44)
  • .github/workflows/nodejs.yml
  • .gitignore
  • README.md
  • build/README.md
  • build/build-cjs.js
  • build/update-windows-zones.js
  • examples/example-rrule-basic.js
  • examples/example-rrule-datefns.js
  • examples/example-rrule-dayjs.js
  • examples/example-rrule-luxon.js
  • examples/example-rrule-moment.js
  • examples/example-rrule-vanilla.js
  • examples/example.js
  • ical.js
  • lib/core-api.js
  • lib/date-utils.js
  • lib/expand-recurring-event.js
  • lib/ical-parser-utils.js
  • lib/public-api.js
  • lib/temporal.js
  • lib/tz-utils.js
  • node-ical.d.ts
  • node-ical.js
  • package.json
  • test/advanced.test.js
  • test/async-error-handling.test.js
  • test/basic.test.js
  • test/commonjs-compat.test.js
  • test/date-only-rrule-until.test.js
  • test/examples-snapshot.test.js
  • test/expand-recurring-event.test.js
  • test/extended-components.test.js
  • test/from-url.test.js
  • test/google-calendar-until-bug.test.js
  • test/issue-459-exchange-recurrence-id.test.js
  • test/meta-preservation.test.js
  • test/module-formats.test.js
  • test/monthly-bymonthday-multiple.test.js
  • test/non-utc-until.test.js
  • test/polyfill-check.test.js
  • test/regression-fixes.test.js
  • test/tsconfig.json
  • test/tz-utils.test.js
  • tsconfig.json
🚧 Files skipped from review as they are similar to previous changes (36)
  • examples/example.js
  • test/date-only-rrule-until.test.js
  • lib/temporal.js
  • test/from-url.test.js
  • test/non-utc-until.test.js
  • test/tz-utils.test.js
  • test/issue-459-exchange-recurrence-id.test.js
  • test/google-calendar-until-bug.test.js
  • test/meta-preservation.test.js
  • .gitignore
  • build/README.md
  • examples/example-rrule-luxon.js
  • examples/example-rrule-moment.js
  • test/extended-components.test.js
  • test/tsconfig.json
  • lib/public-api.js
  • examples/example-rrule-basic.js
  • .github/workflows/nodejs.yml
  • tsconfig.json
  • examples/example-rrule-vanilla.js
  • build/build-cjs.js
  • lib/core-api.js
  • examples/example-rrule-datefns.js
  • README.md
  • test/monthly-bymonthday-multiple.test.js
  • lib/date-utils.js
  • lib/tz-utils.js
  • test/basic.test.js
  • package.json
  • test/regression-fixes.test.js
  • node-ical.d.ts
  • node-ical.js
  • test/module-formats.test.js
  • build/update-windows-zones.js
  • ical.js
  • lib/expand-recurring-event.js

Comment thread lib/ical-parser-utils.js Outdated
Comment thread lib/ical-parser-utils.js Outdated
Comment thread test/commonjs-compat.test.js Outdated
Comment thread test/commonjs-compat.test.js

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/ical-parser-utils.js`:
- Around line 11-18: Update parseParameters() to normalize parameter names to
uppercase at the parsing boundary, and make raw enumerated-value checks
case-insensitive throughout parseValue and the referenced parameter-handling
paths. Ensure mixed-case TZID, VALUE=DATE, FBTYPE, and TRUE/FALSE inputs follow
the same behavior as uppercase forms, with tests covering each case.
- Around line 744-760: Update the returned parser from freebusyParameter to
split value on commas and process each period independently, creating and
appending a distinct fb object for every period. For each period, split on "/"
and pass its start and end components to dateParameter, preserving parameter
storage and support for both single and multiple FREEBUSY periods.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e3849379-bdb0-4544-b76c-84716773f6b7

📥 Commits

Reviewing files that changed from the base of the PR and between 666cdc5 and 5210e72.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (44)
  • .github/workflows/nodejs.yml
  • .gitignore
  • README.md
  • build/README.md
  • build/build-cjs.js
  • build/update-windows-zones.js
  • examples/example-rrule-basic.js
  • examples/example-rrule-datefns.js
  • examples/example-rrule-dayjs.js
  • examples/example-rrule-luxon.js
  • examples/example-rrule-moment.js
  • examples/example-rrule-vanilla.js
  • examples/example.js
  • ical.js
  • lib/core-api.js
  • lib/date-utils.js
  • lib/expand-recurring-event.js
  • lib/ical-parser-utils.js
  • lib/public-api.js
  • lib/temporal.js
  • lib/tz-utils.js
  • node-ical.d.ts
  • node-ical.js
  • package.json
  • test/advanced.test.js
  • test/async-error-handling.test.js
  • test/basic.test.js
  • test/commonjs-compat.test.js
  • test/date-only-rrule-until.test.js
  • test/examples-snapshot.test.js
  • test/expand-recurring-event.test.js
  • test/extended-components.test.js
  • test/from-url.test.js
  • test/google-calendar-until-bug.test.js
  • test/issue-459-exchange-recurrence-id.test.js
  • test/meta-preservation.test.js
  • test/module-formats.test.js
  • test/monthly-bymonthday-multiple.test.js
  • test/non-utc-until.test.js
  • test/polyfill-check.test.js
  • test/regression-fixes.test.js
  • test/tsconfig.json
  • test/tz-utils.test.js
  • tsconfig.json
🚧 Files skipped from review as they are similar to previous changes (34)
  • test/meta-preservation.test.js
  • test/commonjs-compat.test.js
  • lib/public-api.js
  • lib/temporal.js
  • test/tsconfig.json
  • build/README.md
  • test/non-utc-until.test.js
  • test/tz-utils.test.js
  • tsconfig.json
  • build/build-cjs.js
  • examples/example-rrule-moment.js
  • test/date-only-rrule-until.test.js
  • test/issue-459-exchange-recurrence-id.test.js
  • .gitignore
  • test/regression-fixes.test.js
  • test/expand-recurring-event.test.js
  • .github/workflows/nodejs.yml
  • README.md
  • test/google-calendar-until-bug.test.js
  • lib/date-utils.js
  • node-ical.d.ts
  • lib/core-api.js
  • examples/example-rrule-luxon.js
  • lib/tz-utils.js
  • test/extended-components.test.js
  • node-ical.js
  • test/advanced.test.js
  • package.json
  • test/module-formats.test.js
  • build/update-windows-zones.js
  • test/async-error-handling.test.js
  • test/from-url.test.js
  • ical.js
  • lib/expand-recurring-event.js

Comment thread lib/ical-parser-utils.js
Comment thread lib/ical-parser-utils.js
@KristjanESPERANTO

Copy link
Copy Markdown
Contributor Author

Most of the nitpicks are valid, even if they are a bit out of scope. I'm currently working on addressing them.

@KristjanESPERANTO
KristjanESPERANTO force-pushed the esm branch 2 times, most recently from 3d54949 to e921362 Compare July 17, 2026 18:47
@KristjanESPERANTO

Copy link
Copy Markdown
Contributor Author

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
✅ Action performed

Reviews resumed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/ical-parser-utils.js`:
- Around line 277-279: Restrict the applyImplicitEndDate call to supported
components that define a start value, excluding VTIMEZONE and other components
without start/end semantics. Update the condition around applyImplicitEndDate
while preserving the existing utcAdd behavior for valid start-bearing
components.
- Around line 481-483: Update the parameter handling around the `data`
construction and the related URI-repair logic to compare parameter names and
values case-insensitively. Ensure variants such as `value=text`,
`charset=UTF-8`, and `tzid=tzone` follow the same branches as their canonical
forms while preserving the original parameter values in parsed output.
- Around line 750-757: Update the categoriesParameter parser to split CATEGORIES
values only on unescaped commas, preserving escaped commas within a category,
and map each resulting category through text() so escape sequences are decoded.
Apply the same behavior when initializing curr[name] and when appending
additional values.

In `@test/regression-fixes.test.js`:
- Around line 44-73: Update the assertions in the full-day recurring duration
test to use UTC getters, or explicitly pin the test timezone, so results are
independent of the host environment. Keep the expected April 2 start and April 3
midnight end behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dbb99d0a-e8a5-4f0d-8f08-5f1aa8a14c5f

📥 Commits

Reviewing files that changed from the base of the PR and between 5210e72 and e921362.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (44)
  • .github/workflows/nodejs.yml
  • .gitignore
  • README.md
  • build/README.md
  • build/build-cjs.js
  • build/update-windows-zones.js
  • examples/example-rrule-basic.js
  • examples/example-rrule-datefns.js
  • examples/example-rrule-dayjs.js
  • examples/example-rrule-luxon.js
  • examples/example-rrule-moment.js
  • examples/example-rrule-vanilla.js
  • examples/example.js
  • ical.js
  • lib/core-api.js
  • lib/date-utils.js
  • lib/expand-recurring-event.js
  • lib/ical-parser-utils.js
  • lib/public-api.js
  • lib/temporal.js
  • lib/tz-utils.js
  • node-ical.d.ts
  • node-ical.js
  • package.json
  • test/advanced.test.js
  • test/async-error-handling.test.js
  • test/basic.test.js
  • test/commonjs-compat.test.js
  • test/date-only-rrule-until.test.js
  • test/examples-snapshot.test.js
  • test/expand-recurring-event.test.js
  • test/extended-components.test.js
  • test/from-url.test.js
  • test/google-calendar-until-bug.test.js
  • test/issue-459-exchange-recurrence-id.test.js
  • test/meta-preservation.test.js
  • test/module-formats.test.js
  • test/monthly-bymonthday-multiple.test.js
  • test/non-utc-until.test.js
  • test/polyfill-check.test.js
  • test/regression-fixes.test.js
  • test/tsconfig.json
  • test/tz-utils.test.js
  • tsconfig.json
🚧 Files skipped from review as they are similar to previous changes (38)
  • lib/temporal.js
  • build/build-cjs.js
  • lib/public-api.js
  • test/tz-utils.test.js
  • test/date-only-rrule-until.test.js
  • test/meta-preservation.test.js
  • examples/example-rrule-basic.js
  • test/google-calendar-until-bug.test.js
  • .gitignore
  • examples/example-rrule-dayjs.js
  • examples/example-rrule-luxon.js
  • test/extended-components.test.js
  • .github/workflows/nodejs.yml
  • test/tsconfig.json
  • examples/example.js
  • tsconfig.json
  • examples/example-rrule-vanilla.js
  • test/from-url.test.js
  • test/commonjs-compat.test.js
  • build/README.md
  • test/basic.test.js
  • lib/date-utils.js
  • test/non-utc-until.test.js
  • test/issue-459-exchange-recurrence-id.test.js
  • test/monthly-bymonthday-multiple.test.js
  • examples/example-rrule-datefns.js
  • node-ical.d.ts
  • README.md
  • node-ical.js
  • test/module-formats.test.js
  • test/expand-recurring-event.test.js
  • lib/tz-utils.js
  • test/async-error-handling.test.js
  • build/update-windows-zones.js
  • examples/example-rrule-moment.js
  • package.json
  • ical.js
  • lib/expand-recurring-event.js

Comment thread lib/ical-parser-utils.js Outdated
Comment thread lib/ical-parser-utils.js
Comment thread lib/ical-parser-utils.js
Comment thread test/regression-fixes.test.js
@KristjanESPERANTO
KristjanESPERANTO marked this pull request as draft July 17, 2026 19:24
@KristjanESPERANTO
KristjanESPERANTO marked this pull request as ready for review July 17, 2026 22:14

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
node-ical.d.ts (1)

112-128: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Explicitly use declare for ambient const declarations.

If this block is at the top level of the .d.ts module, omitting declare or an initializer could cause a TypeScript TS1155 compilation error for consumers. Even if it is nested within an ambient namespace or module where declare is implicit, adding it explicitly improves readability and aligns with standard type definition practices.

✨ Proposed refactor
-  const _default: {
+  declare const _default: {
     fromURL: typeof fromURL;
     parseFile: typeof parseFile;
     parseICS: typeof parseICS;
     sync: typeof sync;
     async: typeof async;
     expandRecurringEvent: typeof expandRecurringEvent;
     /** Internal compatibility hooks; intentionally left loose to avoid encouraging direct use. */
     objectHandlers: unknown;
     /** Internal compatibility hooks; intentionally left loose to avoid encouraging direct use. */
     handleObject: unknown;
     /** Internal compatibility hooks; intentionally left loose to avoid encouraging direct use. */
     parseLines: unknown;
   };

   export default _default;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@node-ical.d.ts` around lines 112 - 128, Update the top-level ambient constant
declaration for _default to explicitly use declare, while preserving its
existing property types and default export.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/ical-parser-utils.js`:
- Around line 118-139: Update applyUidSequenceMerge and the accepted-revision
merge path to clear prior parsed event fields before copyRecordFields, so
removed properties such as RRULE do not persist; preserve object identity and
parser-owned state including recurrences and inherited method. Retain the
existing recurrence/base-series handling and SEQUENCE precedence, and add
coverage for a higher-SEQUENCE revision removing an optional property.

---

Nitpick comments:
In `@node-ical.d.ts`:
- Around line 112-128: Update the top-level ambient constant declaration for
_default to explicitly use declare, while preserving its existing property types
and default export.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 333aa127-d546-435f-93df-0ad36387d74c

📥 Commits

Reviewing files that changed from the base of the PR and between e921362 and 6080d9c.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (44)
  • .github/workflows/nodejs.yml
  • .gitignore
  • README.md
  • build/README.md
  • build/build-cjs.js
  • build/update-windows-zones.js
  • examples/example-rrule-basic.js
  • examples/example-rrule-datefns.js
  • examples/example-rrule-dayjs.js
  • examples/example-rrule-luxon.js
  • examples/example-rrule-moment.js
  • examples/example-rrule-vanilla.js
  • examples/example.js
  • ical.js
  • lib/core-api.js
  • lib/date-utils.js
  • lib/expand-recurring-event.js
  • lib/ical-parser-utils.js
  • lib/public-api.js
  • lib/temporal.js
  • lib/tz-utils.js
  • node-ical.d.ts
  • node-ical.js
  • package.json
  • test/advanced.test.js
  • test/async-error-handling.test.js
  • test/basic.test.js
  • test/commonjs-compat.test.js
  • test/date-only-rrule-until.test.js
  • test/examples-snapshot.test.js
  • test/expand-recurring-event.test.js
  • test/extended-components.test.js
  • test/from-url.test.js
  • test/google-calendar-until-bug.test.js
  • test/issue-459-exchange-recurrence-id.test.js
  • test/meta-preservation.test.js
  • test/module-formats.test.js
  • test/monthly-bymonthday-multiple.test.js
  • test/non-utc-until.test.js
  • test/polyfill-check.test.js
  • test/regression-fixes.test.js
  • test/tsconfig.json
  • test/tz-utils.test.js
  • tsconfig.json
🚧 Files skipped from review as they are similar to previous changes (32)
  • test/meta-preservation.test.js
  • lib/temporal.js
  • test/commonjs-compat.test.js
  • .github/workflows/nodejs.yml
  • test/google-calendar-until-bug.test.js
  • examples/example-rrule-vanilla.js
  • test/tsconfig.json
  • examples/example-rrule-datefns.js
  • examples/example-rrule-basic.js
  • test/date-only-rrule-until.test.js
  • build/build-cjs.js
  • test/extended-components.test.js
  • lib/public-api.js
  • test/tz-utils.test.js
  • test/advanced.test.js
  • lib/core-api.js
  • build/README.md
  • test/issue-459-exchange-recurrence-id.test.js
  • examples/example-rrule-moment.js
  • lib/date-utils.js
  • test/module-formats.test.js
  • build/update-windows-zones.js
  • test/non-utc-until.test.js
  • test/regression-fixes.test.js
  • tsconfig.json
  • examples/example-rrule-luxon.js
  • node-ical.js
  • README.md
  • package.json
  • test/async-error-handling.test.js
  • ical.js
  • lib/expand-recurring-event.js

Comment thread lib/ical-parser-utils.js

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
lib/ical-parser-utils.js (1)

133-141: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Fully replace override-first entries when the base series arrives.

This branch still overlays base fields onto the override, leaving override-only properties on the resulting base event. Clear removed fields before copying; recurrences and method are already preserved.

Proposed fix
   if (existingIsRecurrence && incomingIsBaseSeries) {
+    clearFieldsRemovedInRevision(existingEntry, incomingEntry);
     copyRecordFields(existingEntry, incomingEntry);
     return;
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/ical-parser-utils.js` around lines 133 - 141, Update
applyUidSequenceMerge’s existingIsRecurrence/incomingIsBaseSeries branch to
clear the existing override record’s removed fields before copying base-series
fields, while preserving recurrences and method as currently handled. Ensure the
resulting entry is fully replaced by the incoming base series rather than
retaining override-only properties.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/ical-parser-utils.js`:
- Around line 342-347: Update the supportsImplicitEnd condition in the implicit
end derivation flow to allow only VEVENT and VTODO components with a Date-valued
start; remove VJOURNAL so applyImplicitEndDate is never called for journal
entries.
- Around line 284-297: Update applyDurationToDate and the related FREEBUSY
duration parsing path to accept an optional leading + sign and match duration
designators case-insensitively. Normalize matched unit letters before
DURATION_UNITS lookup, while preserving existing negative-duration handling and
date parsing for non-duration values.

---

Duplicate comments:
In `@lib/ical-parser-utils.js`:
- Around line 133-141: Update applyUidSequenceMerge’s
existingIsRecurrence/incomingIsBaseSeries branch to clear the existing override
record’s removed fields before copying base-series fields, while preserving
recurrences and method as currently handled. Ensure the resulting entry is fully
replaced by the incoming base series rather than retaining override-only
properties.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f7d7f539-3bda-4be8-8cf9-d5d4134cdb70

📥 Commits

Reviewing files that changed from the base of the PR and between 6080d9c and 71376a8.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (44)
  • .github/workflows/nodejs.yml
  • .gitignore
  • README.md
  • build/README.md
  • build/build-cjs.js
  • build/update-windows-zones.js
  • examples/example-rrule-basic.js
  • examples/example-rrule-datefns.js
  • examples/example-rrule-dayjs.js
  • examples/example-rrule-luxon.js
  • examples/example-rrule-moment.js
  • examples/example-rrule-vanilla.js
  • examples/example.js
  • ical.js
  • lib/core-api.js
  • lib/date-utils.js
  • lib/expand-recurring-event.js
  • lib/ical-parser-utils.js
  • lib/public-api.js
  • lib/temporal.js
  • lib/tz-utils.js
  • node-ical.d.ts
  • node-ical.js
  • package.json
  • test/advanced.test.js
  • test/async-error-handling.test.js
  • test/basic.test.js
  • test/commonjs-compat.test.js
  • test/date-only-rrule-until.test.js
  • test/examples-snapshot.test.js
  • test/expand-recurring-event.test.js
  • test/extended-components.test.js
  • test/from-url.test.js
  • test/google-calendar-until-bug.test.js
  • test/issue-459-exchange-recurrence-id.test.js
  • test/meta-preservation.test.js
  • test/module-formats.test.js
  • test/monthly-bymonthday-multiple.test.js
  • test/non-utc-until.test.js
  • test/polyfill-check.test.js
  • test/regression-fixes.test.js
  • test/tsconfig.json
  • test/tz-utils.test.js
  • tsconfig.json
🚧 Files skipped from review as they are similar to previous changes (33)
  • lib/temporal.js
  • test/commonjs-compat.test.js
  • test/google-calendar-until-bug.test.js
  • test/monthly-bymonthday-multiple.test.js
  • test/expand-recurring-event.test.js
  • test/issue-459-exchange-recurrence-id.test.js
  • examples/example-rrule-moment.js
  • examples/example-rrule-dayjs.js
  • test/date-only-rrule-until.test.js
  • test/meta-preservation.test.js
  • lib/public-api.js
  • test/tz-utils.test.js
  • examples/example-rrule-basic.js
  • test/non-utc-until.test.js
  • tsconfig.json
  • build/build-cjs.js
  • .gitignore
  • test/tsconfig.json
  • test/async-error-handling.test.js
  • examples/example-rrule-vanilla.js
  • examples/example-rrule-datefns.js
  • node-ical.d.ts
  • lib/core-api.js
  • package.json
  • test/module-formats.test.js
  • test/regression-fixes.test.js
  • test/from-url.test.js
  • lib/tz-utils.js
  • README.md
  • node-ical.js
  • build/update-windows-zones.js
  • ical.js
  • lib/expand-recurring-event.js

Comment thread lib/ical-parser-utils.js
Comment thread lib/ical-parser-utils.js Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/ical-parser-utils.js`:
- Around line 705-745: Update the no-TZID handling around
fallbackWithStackTimezone so floating DATE-TIME values return the local
wall-clock Date directly without consulting stack VTIMEZONE data. Apply this to
both no-TZID branches, including the corresponding logic near the alternate
referenced location, while preserving timezone resolution for values that
explicitly provide a TZID.

In `@test/regression-fixes.test.js`:
- Around line 45-46: Update the full-day recurring duration test in the relevant
it block to capture the existing process.env.TZ value, set the UTC timezone only
for the test, and restore the original value in a finally block, deleting the
variable when it was previously unset.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ae63a25c-acf9-4607-a70d-b223f2083949

📥 Commits

Reviewing files that changed from the base of the PR and between 71376a8 and 78b185f.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (44)
  • .github/workflows/nodejs.yml
  • .gitignore
  • README.md
  • build/README.md
  • build/build-cjs.js
  • build/update-windows-zones.js
  • examples/example-rrule-basic.js
  • examples/example-rrule-datefns.js
  • examples/example-rrule-dayjs.js
  • examples/example-rrule-luxon.js
  • examples/example-rrule-moment.js
  • examples/example-rrule-vanilla.js
  • examples/example.js
  • ical.js
  • lib/core-api.js
  • lib/date-utils.js
  • lib/expand-recurring-event.js
  • lib/ical-parser-utils.js
  • lib/public-api.js
  • lib/temporal.js
  • lib/tz-utils.js
  • node-ical.d.ts
  • node-ical.js
  • package.json
  • test/advanced.test.js
  • test/async-error-handling.test.js
  • test/basic.test.js
  • test/commonjs-compat.test.js
  • test/date-only-rrule-until.test.js
  • test/examples-snapshot.test.js
  • test/expand-recurring-event.test.js
  • test/extended-components.test.js
  • test/from-url.test.js
  • test/google-calendar-until-bug.test.js
  • test/issue-459-exchange-recurrence-id.test.js
  • test/meta-preservation.test.js
  • test/module-formats.test.js
  • test/monthly-bymonthday-multiple.test.js
  • test/non-utc-until.test.js
  • test/polyfill-check.test.js
  • test/regression-fixes.test.js
  • test/tsconfig.json
  • test/tz-utils.test.js
  • tsconfig.json
🚧 Files skipped from review as they are similar to previous changes (32)
  • .gitignore
  • test/commonjs-compat.test.js
  • test/date-only-rrule-until.test.js
  • test/google-calendar-until-bug.test.js
  • test/meta-preservation.test.js
  • test/issue-459-exchange-recurrence-id.test.js
  • test/tz-utils.test.js
  • test/basic.test.js
  • examples/example.js
  • test/expand-recurring-event.test.js
  • .github/workflows/nodejs.yml
  • test/tsconfig.json
  • lib/public-api.js
  • examples/example-rrule-basic.js
  • examples/example-rrule-luxon.js
  • test/monthly-bymonthday-multiple.test.js
  • test/advanced.test.js
  • build/build-cjs.js
  • lib/temporal.js
  • test/from-url.test.js
  • tsconfig.json
  • lib/core-api.js
  • test/non-utc-until.test.js
  • build/README.md
  • lib/date-utils.js
  • package.json
  • node-ical.d.ts
  • ical.js
  • test/module-formats.test.js
  • node-ical.js
  • lib/tz-utils.js
  • lib/expand-recurring-event.js

Comment thread lib/ical-parser-utils.js
Comment thread test/regression-fixes.test.js

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/ical-parser-utils.js`:
- Around line 285-300: The applyDurationToDate function currently extracts
valid-looking fragments from malformed duration strings; replace this
partial-match approach with full RFC-duration validation/parsing before calling
utcAdd, rejecting values with missing required designators, unsupported formats,
or trailing/embedded garbage. Preserve the existing unit application and sign
behavior for fully valid durations, and add regression coverage for partially
valid inputs such as P1M and values with trailing garbage.
- Around line 134-142: Update the base-series detection in the merge branch
around existingIsRecurrence and incomingIsBaseSeries so any incoming entry
without recurrenceid is treated as the base component, including RDATE-only
entries. Remove the unnecessary rrule requirement while preserving the existing
clearFieldsRemovedInRevision, copyRecordFields, and return behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 66638e63-e052-4f4b-a980-6e4430c2206b

📥 Commits

Reviewing files that changed from the base of the PR and between 78b185f and 917d505.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (44)
  • .github/workflows/nodejs.yml
  • .gitignore
  • README.md
  • build/README.md
  • build/build-cjs.js
  • build/update-windows-zones.js
  • examples/example-rrule-basic.js
  • examples/example-rrule-datefns.js
  • examples/example-rrule-dayjs.js
  • examples/example-rrule-luxon.js
  • examples/example-rrule-moment.js
  • examples/example-rrule-vanilla.js
  • examples/example.js
  • ical.js
  • lib/core-api.js
  • lib/date-utils.js
  • lib/expand-recurring-event.js
  • lib/ical-parser-utils.js
  • lib/public-api.js
  • lib/temporal.js
  • lib/tz-utils.js
  • node-ical.d.ts
  • node-ical.js
  • package.json
  • test/advanced.test.js
  • test/async-error-handling.test.js
  • test/basic.test.js
  • test/commonjs-compat.test.js
  • test/date-only-rrule-until.test.js
  • test/examples-snapshot.test.js
  • test/expand-recurring-event.test.js
  • test/extended-components.test.js
  • test/from-url.test.js
  • test/google-calendar-until-bug.test.js
  • test/issue-459-exchange-recurrence-id.test.js
  • test/meta-preservation.test.js
  • test/module-formats.test.js
  • test/monthly-bymonthday-multiple.test.js
  • test/non-utc-until.test.js
  • test/polyfill-check.test.js
  • test/regression-fixes.test.js
  • test/tsconfig.json
  • test/tz-utils.test.js
  • tsconfig.json
🚧 Files skipped from review as they are similar to previous changes (41)
  • .gitignore
  • test/date-only-rrule-until.test.js
  • lib/public-api.js
  • test/commonjs-compat.test.js
  • build/README.md
  • test/monthly-bymonthday-multiple.test.js
  • test/meta-preservation.test.js
  • build/build-cjs.js
  • test/from-url.test.js
  • test/issue-459-exchange-recurrence-id.test.js
  • examples/example-rrule-dayjs.js
  • test/tsconfig.json
  • test/expand-recurring-event.test.js
  • examples/example-rrule-luxon.js
  • examples/example-rrule-basic.js
  • test/google-calendar-until-bug.test.js
  • test/tz-utils.test.js
  • examples/example.js
  • test/extended-components.test.js
  • test/non-utc-until.test.js
  • .github/workflows/nodejs.yml
  • examples/example-rrule-datefns.js
  • lib/temporal.js
  • examples/example-rrule-moment.js
  • lib/core-api.js
  • test/advanced.test.js
  • lib/date-utils.js
  • lib/tz-utils.js
  • test/module-formats.test.js
  • tsconfig.json
  • test/async-error-handling.test.js
  • test/basic.test.js
  • node-ical.d.ts
  • package.json
  • README.md
  • node-ical.js
  • examples/example-rrule-vanilla.js
  • ical.js
  • build/update-windows-zones.js
  • lib/expand-recurring-event.js
  • test/regression-fixes.test.js

Comment thread lib/ical-parser-utils.js
Comment thread lib/ical-parser-utils.js Outdated
@jens-maus

Copy link
Copy Markdown
Owner

Thanks @KristjanESPERANTO for addressing all coderabbitai concerns and tremendously working on that nice ESM migration. Will merge it now to get going and hopefully prepare a new 0.27.0 release after testing it throughoutly ASAP.

@jens-maus
jens-maus merged commit 7a17053 into jens-maus:master Jul 18, 2026
14 checks passed
@KristjanESPERANTO

Copy link
Copy Markdown
Contributor Author

Yeah, at times it felt like a never-ending story 😅 Most of the comments were actually outside the scope. But they were valid.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants